#ifndef ODBC_BASIC_CONNECTION_HPP
#define ODBC_BASIC_CONNECTION_HPP

#include "odbc/transaction_policy_concept.hpp"
#include "odbc/details/os_sql_wrapper.hpp"
#include "odbc/environment.hpp"
#include "odbc/odbc_error.hpp"
#include <string>


namespace odbc
{

	namespace details { template< class Connection> class connection_visitor; }

/// \ingroup odbc
/// \brief 
///		It implements the ODBC connection that is used by the statement objects for connecting to the database.
/// \param TransactionPolicy
///			provides the way of treating a transaction. See odbc::transaction_policy_concept
/// \param VerNo
///			The version of ODBC driver manager.
/// \details
///		The basic connection is designed to be decoupled of the transaction model that is used. In principle, there is 
///		an automatic committing transaction model and a manual commit/rollback transaction model (see 
///		odbc::auto_commit_transaction_policy for details on how each transaction model behaves).
///	\see 
///		odbc::transaction_policy_concept for details on how the concept is integrated in the connection class.
template< class TransactionPolicy, unsigned int VerNo = 3>
class basic_connection: public environment_visitor< VerNo>
{
	BOOST_CONCEPT_ASSERT( (transaction_policy_concept< TransactionPolicy>));

	friend class details::connection_visitor< basic_connection< TransactionPolicy, VerNo> >;

public:
	/// \brief It creates an ODBC connection, using the parameters specified in the connection string.
	/// \param conn_str the connection string (ODBC connection string) to be used for connecting.
	/// \param time_out the timeout of the connection.
	/// \details
	///		It uses the provided transaction policy to setup the way the transactions are handled.
	/// \throw odbc_error
	///		In case an error occurs.
	basic_connection( const std::string& conn_str, unsigned int time_out)
		: handle_( SQL_NULL_HANDLE)
		, conn_str_( conn_str)
	{
		try
		{
			// Allocate the handle
			this->check_success(
				SQLAllocHandle( SQL_HANDLE_DBC, environment_visitor<VerNo>::get_env_handle( env_), &handle_));
			// Set the access mode to read-write.
			this->check_success(
				SQLSetConnectAttr( handle_, SQL_ATTR_ACCESS_MODE, SQLPOINTER( SQL_MODE_READ_WRITE), SQL_IS_UINTEGER));

			TransactionPolicy::setup( handle_);

			// Set the timeout
			this->check_success(
				SQLSetConnectAttr( handle_, SQL_ATTR_CONNECTION_TIMEOUT, SQLPOINTER( SQLUINTEGER( time_out)), SQL_IS_UINTEGER));

			// Open the connection
			this->check_success(
				SQLDriverConnect( handle_, NULL, (SQLCHAR*)conn_str_.c_str(), (SQLSMALLINT)conn_str_.size(),
									NULL, 0, NULL, SQL_DRIVER_NOPROMPT));
		}
		catch( ...)
		{
			SQLFreeHandle( SQL_HANDLE_DBC, handle_);
			throw;
		}
	}


	/// \brief It closes the connection and frees the resources.
	~basic_connection()
	{
		if( SQL_NULL_HANDLE != handle_)
		{
			SQLDisconnect( handle_);
			SQLFreeHandle( SQL_HANDLE_DBC, handle_);
		}
	}


	/// \brief It commits the changes made since the connection was established or since the latest time commit or 
	///		rollback was performed. 
	/// \details
	///		It only delegates the call to the transaction policy.
	void commit()
	{
		TransactionPolicy::commit( handle_);
	}

	/// \brief It rolls back the changes made since the connection was established or since the latest time commit or 
	///		rollback was performed. 
	/// \details
	///		It only delegates the call to the transaction policy.
	void rollback()
	{
		TransactionPolicy::rollback( handle_);
	}

	/// \brief It gets the auto-commit features status of the connection.
	bool auto_commit() const
	{
		return static_cast< bool>( TransactionPolicy::has_auto_commit);
	}

private:
	/// \internal
	/// \brief It holds the environment wrapper object that the connection requires for initialization.
	environment<VerNo> env_;

	/// \internal
	/// \brief It holds the ODBC handle of the connection.
	SQLHANDLE handle_;

	/// \internal
	/// \brief The connection string used for connection.
	std::string conn_str_;

	void check_success( SQLRETURN ret_code)
	{
		if( ret_code != SQL_SUCCESS && ret_code != SQL_SUCCESS_WITH_INFO)
		{
			throw odbc_error( ret_code, handle_, SQL_HANDLE_DBC);
		}
	}
};


} // namespace odbc

#endif // ODBC_BASIC_CONNECTION_HPP
